#include "account_mgr.h"

#include <fstream>

#include <boost/format.hpp>

#include <cppconn/driver.h>
#include <cppconn/exception.h>
#include <cppconn/warning.h>
#include <cppconn/metadata.h>
#include <cppconn/prepared_statement.h>
#include <cppconn/resultset.h>
#include <cppconn/resultset_metadata.h>
#include <cppconn/statement.h>
#include <mysql_driver.h>
#include <mysql_connection.h>

#include <common/base/server_mgr.h>
#include <common/log/log.h>
#include <common/net/session.h>
#include <common/net/session_mgr.h>
#include <common/utils/util.h>

#include "base/db_mgr.h"
#include "base/sub_server_mgr.h"
#include "net/custom_data.h"

bool AccountMgr::init()
{
    if (!initAccount())
    {
        LOG_ERROR("Init Account failed.");
        return false;
    }
    if (!initPlayer())
    {
        LOG_ERROR("Init player failed.");
        return false;
    }

    if (!initRandName())
    {
        LOG_ERROR("Init rand name failed.");
        return false;
    }
    return true;
}

bool AccountMgr::initAccount()
{
    auto subMgr = ServerMgr::get().getSubServ<SubServerMgr>();
    if (!subMgr)
    {
        LOG_ERROR("Sub manager not init.");
        return false;
    }
    auto dbMgr = subMgr->getSubMgr<DBMgr>(SubManagerType::eDB);
    if (!dbMgr)
    {
        LOG_ERROR("db manager not init.");
        return false;
    }

    try
    {
        auto conn = dbMgr->getMysqlConn();
        std::unique_ptr<sql::Statement> stmt(conn->createStatement());
        std::string sql("SELECT acc_id, acc_name, source, area_id, reg_time FROM `account`");
        std::unique_ptr<sql::ResultSet> res(stmt->executeQuery(std::move(sql)));

        std::lock_guard<std::mutex> lock(mutex_);
        while (res->next())
        {
            auto accInfo = std::make_shared<AccountInfo>();
            accInfo->accID = res->getUInt(1);
            accInfo->accName = res->getString(2);
            accInfo->source = res->getString(3);
            accInfo->areaID = res->getUInt(4);
            accInfo->regTime = res->getUInt64(5);
            accounts_.insert(std::make_pair(std::make_pair(accInfo->accName, accInfo->source), accInfo));
            accInfos_.insert(std::make_pair(accInfo->accID, accInfo));
        }
    }
    catch (sql::SQLException &e)
    {
        LOG_ERROR("# ERR: " << e.what()
                  << " (MySQL error code: " << e.getErrorCode()
                  << ", SQLState: " << e.getSQLState() << " )" );
        return false;
    }
    return true;
}



bool AccountMgr::initPlayer()
{
    auto subMgr = ServerMgr::get().getSubServ<SubServerMgr>();
    if (!subMgr)
    {
        LOG_ERROR("Sub manager not init.");
        return false;
    }
    auto dbMgr = subMgr->getSubMgr<DBMgr>(SubManagerType::eDB);
    if (!dbMgr)
    {
        LOG_ERROR("db manager not init.");
        return false;
    }

    try
    {
        auto conn = dbMgr->getMysqlConn();
        std::unique_ptr<sql::Statement> stmt(conn->createStatement());
        std::string sql("SELECT player_id, player_name FROM `player`");
        std::unique_ptr<sql::ResultSet> res(stmt->executeQuery(std::move(sql)));

        std::lock_guard<std::mutex> lock(mutex_);
        while (res->next())
        {
            uint32_t playerID = res->getUInt(1);
            std::string playerName = res->getString(2);
            players_.insert(std::make_pair(playerName, playerID));
        }
    }
    catch (sql::SQLException &e)
    {
        LOG_ERROR("# ERR: " << e.what()
                  << " (MySQL error code: " << e.getErrorCode()
                  << ", SQLState: " << e.getSQLState() << " )" );
        return false;
    }
    return true;
}

bool AccountMgr::initRandName()
{
    std::fstream fsPreNames("first_name.dat");
    if (!fsPreNames.is_open())
    {
        LOG_ERROR("read  prefix  failed!");
        return false;
    }
    for(std::string preName; std::getline(fsPreNames, preName); )
    {
        if (!preName.empty())
        {
            preName.erase(preName.size() - 1);
            preNames_.push_back(preName);
        }
    }
    fsPreNames.close();

    std::fstream fsSufNames("last_name.dat");
    if (!fsSufNames.is_open()) {
        LOG_ERROR("read suffix failed!");
        return false;
    }
    for(std::string sufName; std::getline(fsSufNames, sufName); )
    {
        if (!sufName.empty()) {
            sufName.erase(sufName.size() - 1);
            sufNames_.push_back(sufName);
        }
    }
    fsSufNames.close();
    return true;
}


bool AccountMgr::createAccount(const std::string& accName, const std::string& source, uint32_t& accID)
{
    auto subMgr = ServerMgr::get().getSubServ<SubServerMgr>();
    if (!subMgr)
    {
        LOG_ERROR("Sub manager not init.");
        return false;
    }
    auto dbMgr = subMgr->getSubMgr<DBMgr>(SubManagerType::eDB);
    if (!dbMgr)
    {
        LOG_ERROR("db manager not init.");
        return false;
    }
    {
        std::lock_guard<std::mutex> lock(mutex_);
        auto it = accounts_.find(std::make_pair(accName, source));
        if (it != accounts_.end())
        {
            LOG_ERROR("create account failed, account already exist. "
                      " accName:" << accName << " source:" << source);
            return false;
        }
    }

    auto accInfo = std::make_shared<AccountInfo>();
    accInfo->accID = accID;
    accInfo->accName = accName;
    accInfo->source = source;
    accInfo->areaID = subMgr->getAreaID();
    accInfo->regTime = getCurrentSecond();

    try
    {
        auto conn = dbMgr->getMysqlConn();
        std::unique_ptr<sql::Statement> stmt(conn->createStatement());
        std::string sql = string_format(" select create_account('%1%', '%2%', %3%, %4%)",
                                        accInfo->accName, accInfo->source, accInfo->areaID, accInfo->regTime);
        std::unique_ptr<sql::ResultSet> res(stmt->executeQuery(sql));
        if (res->rowsCount() != 1)
        {
            LOG_ERROR("create account when insert failed. accName:" << accName << " source:" << source);
            return false;
        }
        res->first();
        accInfo->accID = accID = res->getUInt(1);
    }
    catch (sql::SQLException &e)
    {
        LOG_ERROR("# ERR: " << e.what()
                  << " (MySQL error code: " << e.getErrorCode()
                  << ", SQLState: " << e.getSQLState() << " )" );
        return false;
    }

    {
        std::lock_guard<std::mutex> lock(mutex_);
        auto pair = std::make_pair(accName, source);
        if (!accounts_.insert(std::make_pair(pair, accInfo) ).second)
        {
            LOG_ERROR("create account when insert failed. accName:" << accName << " source:" << source);
            return false;
        }
        if (!accInfos_.insert(std::make_pair(accInfo->accID, accInfo)).second)
        {
            LOG_ERROR("create account when insert failed. accName:" << accName << " source:" << source);
            return false;
        }
    }
    return true;
}

std::string AccountMgr::genRandName()
{
    for (uint16_t i = 0; i < 10; ++i)
    {
        std::string name(preNames_[ServerMgr::get().getRandInt()]);
        name.append(sufNames_[ServerMgr::get().getRandInt()]);

        auto it = players_.find(name);
        if (it == players_.end())
            return name;
    }
    // 最多随11次，10次后还重复，直接随机一个，不再检查
    std::string name(preNames_[ServerMgr::get().getRandInt()]);
    name.append(sufNames_[ServerMgr::get().getRandInt()]);
    return name;
}

void AccountMgr::getPlayerList(uint32_t accID, std::vector<PlayerCreateInfo>& players)
{
    auto subMgr = ServerMgr::get().getSubServ<SubServerMgr>();
    if (!subMgr)
    {
        LOG_ERROR("Sub manager not init.");
        return;
    }
    auto dbMgr = subMgr->getSubMgr<DBMgr>(SubManagerType::eDB);
    if (!dbMgr)
    {
        LOG_ERROR("db manager not init.");
        return;
    }

    auto conn = dbMgr->getMysqlConn();
    std::unique_ptr<sql::Statement> stmt(conn->createStatement());
    std::string sql = string_format("select player_id, player_name, head_id, last_login_time "
                                    " from player where acc_id = %1%", accID);
    std::unique_ptr<sql::ResultSet> res(stmt->executeQuery(std::move(sql)));
    while (res->next())
    {
        PlayerCreateInfo info;
        info.playerID = res->getUInt(1);
        info.playerName  = res->getString(2);
        info.headID  = res->getUInt(3);
        info.lastLoginTime = res->getUInt64(4);
        players.push_back(info);
    }
    return;
}

bool AccountMgr::createPlayer(const std::string& playerName, uint32_t accID,
                              uint16_t headID, uint32_t& playerID)
{
    auto subMgr = ServerMgr::get().getSubServ<SubServerMgr>();
    if (!subMgr)
    {
        LOG_ERROR("Sub manager not init.");
        return false;
    }
    auto dbMgr = subMgr->getSubMgr<DBMgr>(SubManagerType::eDB);
    if (!dbMgr)
    {
        LOG_ERROR("db manager not init.");
        return false;
    }
    auto accInfo = getAccInfo(accID);
    if (!accInfo)
    {
        LOG_ERROR("create player failed, account not exist. accId: " << accID);
        return false;
    }

    try
    {
        auto conn = dbMgr->getMysqlConn();
        std::unique_ptr<sql::Statement> stmt(conn->createStatement());
        std::string sql = string_format(" select create_player('%1%', %2%, %3%, %4%)",
                                        playerName, accID, getCurrentSecond(), headID);
        std::unique_ptr<sql::ResultSet> res(stmt->executeQuery(sql));
        if (res->rowsCount() != 1)
        {
            LOG_ERROR("create player when insert failed. playerName:" << playerName << " accID:" << accID);
            return false;
        }
        res->first();
        playerID = res->getUInt(1);
    }
    catch (sql::SQLException &e)
    {
        LOG_ERROR("# ERR: " << e.what()
                  << " (MySQL error code: " << e.getErrorCode()
                  << ", SQLState: " << e.getSQLState() << " )" );
        return false;
    }

    {
        std::lock_guard<std::mutex> lock(mutex_);
        if (!players_.insert(std::make_pair(playerName, playerID )).second)
        {
            LOG_ERROR("create player when insert failed. playerName:" << playerName << " accID:" << accID);
            return false;
        }
    }
    return true;
}

std::shared_ptr<Session> AccountMgr::chooseGsSession(const std::shared_ptr<Session>& cltSession, uint32_t playerID)
{
    auto sessionMgr = ServerMgr::get().getMgr<SessionMgr>(ManagerType::eSession);
    if (!sessionMgr)
    {
        LOG_ERROR("Session manager not found.");
        return std::shared_ptr<Session>();
    }

    std::vector<std::shared_ptr<Session> > sessions;
    sessionMgr->getConnSessions(ServerType::eGameServer, SessionType::eNormal, sessions);

    if (sessions.empty())
    {
        LOG_ERROR("Game server not config.");
        return std::shared_ptr<Session>();
    }

    ++gsIndex_;
    gsIndex_ = gsIndex_ % sessions.size();
    const std::shared_ptr<Session>& gsSession = sessions[gsIndex_];
    if (!gsSession)
    {
        LOG_ERROR("session not found.");
        return gsSession;
    }

    // CltSessionCustomData& cltCustom = getCustom<CltSessionCustomData>(cltSession);
    // cltCustom.playerID = playerID;
    // cltCustom.gsSession = gsSession;

    // GsSessionCustomData& gsCustom = getCustom<GsSessionCustomData>(gsSession);
    // gsCustom.addCltSession(playerID, cltSession);

    return gsSession;
}
